using System.Collections.Generic;
using System;

namespace needle.Services.astra.Collections
{
	/// <summary>
	/// List interface with <see cref="ItemsChange"/> event.
	/// </summary>
	/// <typeparam name="T">List item type.</typeparam>
	public interface IItemsChangeList<T> : IList<T>
	{
		/// <summary>
		/// Fired when one of <see cref="ListItemsChanges"/> occures.
		/// </summary>
		event EventHandler<ListItemsChangeEventArgs<T>> ItemsChange;
	}


	/// <summary>
	/// <see cref="IItemsChangeList{}.ItemsChange"/> event arguments.
	/// </summary>
	/// <typeparam name="T">List item type.</typeparam>
	public class ListItemsChangeEventArgs<T> : EventArgs
	{
		#region Constructors

		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="change">
		/// See <see cref="Change"/>.
		/// Allowed values are <see cref="ListItemsChanges.Add"/> or <see cref="ListItemsChanges.Remove"/>.
		/// </param>
		/// <param name="item"><see cref="NewItem"/> for Add, <see cref="OldItem"/> for Remove.</param>
		/// <param name="index"><see cref="NewIndex"/> for Add, <see cref="OldIndex"/> for Remove.</param>
		/// <exception cref="ArgumentOutOfRangeException">
		/// <paramref name="change"/> is not <see cref="ListItemsChanges.Add"/> or <see cref="ListItemsChanges.Remove"/>.
		/// </exception>
		public ListItemsChangeEventArgs(ListItemsChanges change, T item, int index)
			: this(change,
					change == ListItemsChanges.Add ? item : default(T), change == ListItemsChanges.Add ? index : -1,
					change == ListItemsChanges.Remove ? item : default(T), change == ListItemsChanges.Remove ? index : -1)
		{
			if (change != ListItemsChanges.Add && change != ListItemsChanges.Remove)
				throw new ArgumentOutOfRangeException("change", change, "Must be Add or Remove change.");
		}
		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="change">
		/// See <see cref="Change"/>.
		/// Allowed value is <see cref="ListItemsChanges.Replace"/>
		/// </param>
		/// <param name="newItem">See <see cref="NewItem"/>.</param>
		/// <param name="oldItem">See <see cref="OldItem"/>.</param>
		/// <param name="index">Index for <see cref="NewIndex"/> and <see cref="OldIndex"/>.</param>
		/// <exception cref="ArgumentOutOfRangeException">
		/// <paramref name="change"/> is not <see cref="ListItemsChanges.Replace"/>.
		/// </exception>
		public ListItemsChangeEventArgs(ListItemsChanges change, T newItem, T oldItem, int index)
			: this(change, newItem, index, oldItem, index)
		{
			if (change != ListItemsChanges.Replace)
				throw new ArgumentOutOfRangeException("change", change, "Must be Replace change.");
		}
		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="change">
		/// See <see cref="Change"/>.
		/// Allowed value is <see cref="ListItemsChanges.Move"/>
		/// </param>
		/// <param name="item">Item for <see cref="NewItem"/> and <see cref="OldItem"/>.</param>
		/// <param name="newIndex">See <see cref="NewIndex"/>.</param>
		/// <param name="oldIndex">See <see cref="OldIndex"/>.</param>
		/// <exception cref="ArgumentOutOfRangeException">
		/// <paramref name="change"/> is not <see cref="ListItemsChanges.Move"/>.
		/// </exception>
		public ListItemsChangeEventArgs(ListItemsChanges change, T item, int newIndex, int oldIndex)
			: this(change, item, newIndex, item, oldIndex)
		{
			if (change != ListItemsChanges.Move)
				throw new ArgumentOutOfRangeException("change", change, "Must be Move change.");
		}
		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="change">
		/// See <see cref="Change"/>.
		/// Allowed value is <see cref="ListItemsChanges.Reset"/>.
		/// </param>
		/// <exception cref="ArgumentOutOfRangeException">
		/// <paramref name="change"/> is not <see cref="ListItemsChanges.Reset"/>.
		/// </exception>
		public ListItemsChangeEventArgs(ListItemsChanges change)
			: this(change, default(T), -1, default(T), -1)
		{
			if (change != ListItemsChanges.Reset)
				throw new ArgumentOutOfRangeException("change", change, "Must be Reset change.");
		}

		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="change">See <see cref="Change"/>.</param>
		/// <param name="newItem">See <see cref="NewItem"/>.</param>
		/// <param name="newIndex">See <see cref="NewIndex"/>.</param>
		/// <param name="oldItem">See <see cref="OldItem"/>.</param>
		/// <param name="oldIndex">See <see cref="OldIndex"/>.</param>
		protected ListItemsChangeEventArgs(ListItemsChanges change, T newItem, int newIndex, T oldItem, int oldIndex)
		{
			_change = change;
			_newIndex = newIndex;
			_newItem = newItem;
			_oldIndex = oldIndex;
			_oldItem = oldItem;
		}

		#endregion

		/// <summary>
		/// Type of list item(s) change.
		/// </summary>
		public ListItemsChanges Change
		{
			get { return _change; }
		}
		/// <summary>
		/// Added item for <see cref="ListItemsChanges.Add"/> change.
		/// New replacing item for <see cref="ListItemsChanges.Replace"/> change.
		/// Moved item for <see cref="ListItemsChanges.Move"/> change.
		/// Otherwise default(<typeparamref name="T"/>).
		/// </summary>
		public T NewItem
		{
			get { return _newItem; }
		}
		/// <summary>
		/// Added item index for <see cref="ListItemsChanges.Add"/> change.
		/// New replacing index for <see cref="ListItemsChanges.Replace"/> change.
		/// Moved item index for <see cref="ListItemsChanges.Move"/> change.
		/// Otherwise -1.
		/// </summary>
		public int NewIndex
		{
			get { return _newIndex; }
		}
		/// <summary>
		/// Removed item for <see cref="ListItemsChanges.Remove"/> change.
		/// Old replaced item for <see cref="ListItemsChanges.Replace"/> change.
		/// Moved item for <see cref="ListItemsChanges.Move"/> change.
		/// Otherwise default(<typeparamref name="T"/>).
		/// </summary>
		public T OldItem
		{
			get { return _oldItem; }
		}
		/// <summary>
		/// Removed item index for <see cref="ListItemsChanges.Remove"/> change.
		/// Old replaced item index for <see cref="ListItemsChanges.Replace"/> change.
		/// Moved item index for <see cref="ListItemsChanges.Move"/> change.
		/// Otherwise -1.
		/// </summary>
		public int OldIndex
		{
			get { return _oldIndex; }
		}

		/// <summary>
		/// See <see cref="Change"/>.
		/// </summary>
		private ListItemsChanges _change;
		/// <summary>
		/// See <see cref="NewItem"/>.
		/// </summary>
		private T _newItem;
		/// <summary>
		/// See <see cref="NewIndex"/>.
		/// </summary>
		private int _newIndex;
		/// <summary>
		/// See <see cref="OldItem"/>.
		/// </summary>
		private T _oldItem;
		/// <summary>
		/// See <see cref="OldIndex"/>.
		/// </summary>
		private int _oldIndex;
	}


	/// <summary>
	/// Types of list item(s) change.
	/// </summary>
	public enum ListItemsChanges : byte
	{
		/// <summary>
		/// New item is added.
		/// </summary>
		Add,
		/// <summary>
		/// Item is removed.
		/// </summary>
		Remove,
		/// <summary>
		/// Item is replaced.
		/// </summary>
		Replace,
		/// <summary>
		/// Item is moved.
		/// </summary>
		Move,
		/// <summary>
		/// List items are changed dramatically.
		/// </summary>
		Reset
	}
}